import { defineComponent, ref, reactive, markRaw, watch, onUnmounted, toRaw, onMounted } from 'vue'
import { generateUUID, text2Img, obj2Style, camel2Kebab } from '../utils'
import classes from './ItemNode.module.css'
import {
  ElButton,
  ElForm,
  ElFormItem,
  ElIcon,
  ElInput,
  ElInputNumber,
  ElPopover,
  ElRadioButton,
  ElRadioGroup,
  ElScrollbar,
  ElTabPane,
  ElTabs,
  ElTag,
  ElText
} from 'element-plus'
import { state } from '../store/store.js'
import storeRaw from '../store/store.js?raw'
import Tip from '../panel/Tip.vue'
import { Delete, Edit } from '@element-plus/icons-vue'
import CodeEditor from '../panel/CodeEditor.vue'
import { debounce } from '../utils'

export default class ItemNode {
  uuid = generateUUID()
  isItemNode = true
  static preview = true // 是否在组件面板预览
  path = '' // ../modules.ts里的key
  name = ''
  props: Record<string, any> = reactive({
    style: {
      position: 'relative',
      zIndex: 'auto', // 负值会在非定位元素下面 auto不会创建局部上下级
      boxSizing: 'border-box'
    } as Record<string, any>,
    animation: {
      keyframes: [],
      options: {
        delay: 0,
        duration: 0,
        fill: 'forwards',
        iterations: Infinity
      }
    },
    watchState: [] as watchStateObj[], // 监听的state属性，以及对应的方法
    events: []
  })
  elRef = ref() // 元素
  viewRef = ref() // 预览元素
  component // 内容组件
  view // 左侧预览组件
  params //右侧参数组件
  parent
  children = reactive([])
  watchStateOptions = [{ value: 'show', label: '显隐元素' }] // 依赖变量方法的选项
  styleDisplay = 'block' // 显隐原始display

  /**
   * @param node json解析出的数据对象，非节点对象
   */
  constructor(node?) {
    const _self = this

    Object.defineProperty(this, 'children', { writable: false })

    if (node) {
      this.uuid = node.uuid
      this.path = node.path
      this.name = node.name
      this.props = reactive(node.props)

      node.children.forEach((child) => this.add(child))
    }

    // 动画
    const animate = () => {
      if (!this.props.animation) return

      if (this.props.animation.keyframes.length > 0 && this.elRef?.value) {
        this.props.animation.options.iterations ??= Infinity

        this.elRef.value.animate(this.props.animation.keyframes, this.props.animation.options)
      }
    }

    // 元素事件
    const genEvent = () => ({
      fn: 'function (e) {\n  \n}',
      listener: 'none',
      type: 'none',
      label: 'none'
    })
    const genListener = (event) => {
      if (!event.fn) return

      if (_self || state) event.listener = eval(`(${event.fn})`)
    }
    const addEvent = (event) => {
      this.props.events.push(event)
      genListener(event)
      this.elRef.value?.addEventListener(event.type, event.listener)
    }
    const removeEvent = (event) => {
      const index = this.props.events.indexOf(event)
      this.props.events.splice(index, 1)
      this.elRef.value?.removeEventListener(event.type, event.listener)
    }
    const updateEvent = (event) => {
      this.elRef.value?.removeEventListener(event.type, event.listener)
      genListener(event)
      this.elRef.value?.addEventListener(event.type, event.listener)
    }
    const debounceUpdateEvent = debounce(updateEvent, 1000)

    // markRaw组件非响应
    this.component = markRaw(
      defineComponent((props, { emit, slots }) => {
        let dragImage
        text2Img(this.name).then((res) => (dragImage = res))

        // 自定义事件 并向上冒泡

        // 选中
        const onMousedown = (e) => {
          this.elRef.value = e.currentTarget

          e.stopPropagation()

          const ev: any = new Event('select-node', { bubbles: true, cancelable: false })
          ev.targetNode = this
          this.elRef.value.dispatchEvent(ev)
        }
        // 悬停
        const onMouseover = (e) => {
          if (!this.elRef.value) return

          e.stopPropagation()
          const ev: any = new Event('hover-node', { bubbles: true, cancelable: false })
          ev.targetNode = this
          this.elRef.value.dispatchEvent(ev)
        }
        // 拖拽component
        // dataTransfer只能传输字符串
        const onDragstart = (e) => {
          if (!this.elRef.value) return

          e.stopPropagation()
          e.dataTransfer.setDragImage(dragImage, 0, 0)
          const ev: any = new Event('drag-node', { bubbles: true, cancelable: false })
          ev.targetNode = this
          this.elRef.value.dispatchEvent(ev)
        }
        // dragover
        const onDragover = (e) => {
          e.preventDefault()
        }
        // dragenter
        const onDragenter = (e) => {
          if (!this.elRef.value) return

          e.stopPropagation()
          const ev: any = new Event('dragenter-node', { bubbles: true, cancelable: false })
          ev.targetNode = this
          this.elRef.value.dispatchEvent(ev)
        }
        // drop
        const onDrop = (e) => {
          if (!this.elRef.value) return

          e.stopPropagation()
          const ev: any = new Event('drop-node', { bubbles: true, cancelable: false })
          ev.targetNode = this
          this.elRef.value.dispatchEvent(ev)
        }
        // 解除监听state属性的函数集合
        const watchStateSet = new Set()
        // 显隐元素
        const show = (newValue) => {
          this.props.style.display = newValue ? this.styleDisplay : 'none'
        }
        // 监听依赖的state属性，响应对应方法
        watch(
          () => this.props.watchState,
          () => {
            watchStateSet.forEach((v: () => void) => v())

            this.props.watchState.forEach((watchObj: watchStateObj) => {
              if (watchObj.type == 'show') {
                watchStateSet.add(watch(() => state[watchObj.stateProperty], show))
              }
            })
          },
          { immediate: true, deep: true }
        )

        onMounted(() => {
          this.props.events.forEach((item) => updateEvent(item))
          animate()
        })
        onUnmounted(() => {
          watchStateSet.forEach((v: () => void) => v())
        })

        return () => (
          <div
            ref={this.elRef}
            class={`${classes['item-node']}`}
            style={this.props.style}
            onMousedown={onMousedown}
            onMouseover={onMouseover}
            onDragstart={onDragstart}
            onDragover={onDragover}
            onDragenter={onDragenter}
            onDrop={onDrop}
          >
            {slots.default?.()}
          </div>
        )
      })
    )
    // 预览面板
    this.view = markRaw(
      defineComponent(
        (props: { header: string }, { slots }) => {
          // 拖拽view
          const onDragstart = (e) => {
            e.stopPropagation()
            const ev: any = new Event('drag-node', { bubbles: true, cancelable: false })
            ev.targetNode = new (Object.getPrototypeOf(this).constructor)()
            this.viewRef.value.dispatchEvent(ev)
          }

          return () => (
            <div
              ref={this.viewRef}
              class={classes['view-card']}
              draggable="true"
              onDragstart={onDragstart}
            >
              <div class={classes['view-card-header']}>{this.name}</div>
              <div class={classes['view-card-body']}>{slots.default?.()}</div>
            </div>
          )
        },
        {
          props: ['header']
        }
      )
    )
    // 参数面板
    this.params = markRaw(
      defineComponent((props, { slots }) => {
        return () => (
          <>
            <ElTabs type="border-card">
              <ElTabPane label="参数">
                {{
                  default: () => [
                    // 为展示弹出框，设置最小高度
                    <ElScrollbar view-style={{ minHeight: '500px' }}>
                      <div class={classes['params-item']}>
                        <ElText class={classes['params-item-label']} type="info">
                          uuid
                        </ElText>
                        <div class={classes['params-item-content']} style="font-size: 12px;">
                          {this.uuid}
                        </div>
                      </div>
                      <div class={classes['params-item']}>
                        <ElText class={classes['params-item-label']} type="info">
                          依赖变量
                          <Tip content={'依赖的全局状态state的属性'}></Tip>
                        </ElText>
                        <div class={classes['params-item-content']}>
                          <ElButton
                            type="primary"
                            size="small"
                            onClick={() => {
                              const item: watchStateObj = {
                                stateProperty: 'none',
                                type: 'none'
                              }
                              this.props.watchState.push(item)
                            }}
                          >
                            + 添加依赖
                          </ElButton>
                          <br></br>
                          {this.props.watchState.map((item, index) => (
                            <ElTag
                              closable
                              type="primary"
                              onClose={() => this.props.watchState.splice(index, 1)}
                            >
                              {{
                                default: () => {
                                  return (
                                    <span
                                      style={{
                                        flex: '1',
                                        display: 'flex',
                                        justifyContent: 'space-between'
                                      }}
                                    >
                                      <span>{item.stateProperty}</span>
                                      <span>
                                        <ElPopover
                                          width={250}
                                          trigger="click"
                                          teleported={false}
                                          placement="top"
                                        >
                                          {{
                                            reference: () => (
                                              <ElIcon color="var(--el-color-primary)">
                                                <Edit></Edit>
                                              </ElIcon>
                                            ),
                                            default: () => {
                                              return (
                                                <>
                                                  <ElForm label-width="80px">
                                                    <ElFormItem label="state属性">
                                                      <ElInput
                                                        v-model={item.stateProperty}
                                                      ></ElInput>
                                                    </ElFormItem>
                                                    <ElFormItem label="触发的方法">
                                                      <ElRadioGroup
                                                        v-model={item.type}
                                                        size="small"
                                                      >
                                                        {this.watchStateOptions.map((opt) => (
                                                          <ElRadioButton
                                                            value={opt.value}
                                                            label={opt.label}
                                                            size="small"
                                                          ></ElRadioButton>
                                                        ))}
                                                      </ElRadioGroup>
                                                    </ElFormItem>
                                                  </ElForm>
                                                </>
                                              )
                                            }
                                          }}
                                        </ElPopover>
                                      </span>
                                    </span>
                                  )
                                }
                              }}
                            </ElTag>
                          ))}
                        </div>
                      </div>
                      {slots.default?.()}
                    </ElScrollbar>
                  ]
                }}
              </ElTabPane>
              <ElTabPane label="样式">
                {{
                  default: () => [
                    <ElScrollbar>
                      <div class={classes['params-item']}>
                        <ElText class={classes['params-item-label']} type="info">
                          宽度
                        </ElText>
                        <div class={classes['params-item-content']}>
                          <ElInput
                            modelValue={this.props.style.width}
                            onUpdate:modelValue={(v) => (this.props.style.width = v)}
                            size="small"
                          ></ElInput>
                        </div>
                      </div>
                      <div class={classes['params-item']}>
                        <ElText class={classes['params-item-label']} type="info">
                          高度
                        </ElText>
                        <div class={classes['params-item-content']}>
                          <ElInput
                            modelValue={this.props.style.height}
                            onUpdate:modelValue={(v) => (this.props.style.height = v)}
                            size="small"
                          ></ElInput>
                        </div>
                      </div>
                      <div class={classes['params-item']}>
                        <ElText class={classes['params-item-label']} type="info">
                          内边距
                        </ElText>
                        <div class={`${classes['params-item-content']} ${classes['distance-box']}`}>
                          <ElInput
                            class={classes['distance-left']}
                            modelValue={this.props.style.paddingLeft}
                            onUpdate:modelValue={(v) => (this.props.style.paddingLeft = v)}
                            placeholder="左"
                            size="small"
                          ></ElInput>
                          <ElInput
                            class={classes['distance-right']}
                            modelValue={this.props.style.paddingRight}
                            onUpdate:modelValue={(v) => (this.props.style.paddingRight = v)}
                            placeholder="右"
                            size="small"
                          ></ElInput>
                          <ElInput
                            class={classes['distance-top']}
                            modelValue={this.props.style.paddingTop}
                            onUpdate:modelValue={(v) => (this.props.style.paddingTop = v)}
                            placeholder="上"
                            size="small"
                          ></ElInput>
                          <ElInput
                            class={classes['distance-bottom']}
                            modelValue={this.props.style.paddingBottom}
                            onUpdate:modelValue={(v) => (this.props.style.paddingBottom = v)}
                            placeholder="下"
                            size="small"
                          ></ElInput>
                        </div>
                      </div>
                      <div class={classes['params-item']}>
                        <ElText class={classes['params-item-label']} type="info">
                          外边距
                        </ElText>
                        <div class={`${classes['params-item-content']} ${classes['distance-box']}`}>
                          <ElInput
                            class={classes['distance-left']}
                            modelValue={this.props.style.marginLeft}
                            onUpdate:modelValue={(v) => (this.props.style.marginLeft = v)}
                            placeholder="左"
                            size="small"
                          ></ElInput>
                          <ElInput
                            class={classes['distance-right']}
                            modelValue={this.props.style.marginRight}
                            onUpdate:modelValue={(v) => (this.props.style.marginRight = v)}
                            placeholder="右"
                            size="small"
                          ></ElInput>
                          <ElInput
                            class={classes['distance-top']}
                            modelValue={this.props.style.marginTop}
                            onUpdate:modelValue={(v) => (this.props.style.marginTop = v)}
                            placeholder="上"
                            size="small"
                          ></ElInput>
                          <ElInput
                            class={classes['distance-bottom']}
                            modelValue={this.props.style.marginBottom}
                            onUpdate:modelValue={(v) => (this.props.style.marginBottom = v)}
                            placeholder="下"
                            size="small"
                          ></ElInput>
                        </div>
                      </div>
                      <div class={classes['params-item']}>
                        <ElText class={classes['params-item-label']} type="info">
                          定位
                        </ElText>
                        <div class={classes['params-item-content']}>
                          <ElRadioGroup
                            modelValue={this.props.style.position}
                            onUpdate:modelValue={(v) => {
                              this.props.style.position = v
                              this.props.style.zIndex =
                                this.props.style.left =
                                this.props.style.top =
                                  'auto'
                            }}
                            size="small"
                          >
                            <ElRadioButton
                              value="relative"
                              label="相对"
                              size="small"
                            ></ElRadioButton>
                            <ElRadioButton
                              value="absolute"
                              label="绝对"
                              size="small"
                            ></ElRadioButton>
                          </ElRadioGroup>
                        </div>
                      </div>
                      <div class={classes['params-item']}>
                        <ElText class={classes['params-item-label']} type="info">
                          层级
                        </ElText>
                        <div class={classes['params-item-content']}>
                          <ElInputNumber
                            modelValue={Number(this.props.style.zIndex)}
                            onUpdate:modelValue={(v) =>
                              (this.props.style.zIndex = v?.toString() ?? 'auto')
                            }
                            min={0}
                            controls-position="right"
                            size="small"
                          ></ElInputNumber>
                        </div>
                      </div>
                      <>
                        {this.props.style.position == 'absolute' && [
                          <div class={classes['params-item']}>
                            {/* 简写属性inset会有冲突 */}
                            <ElText class={classes['params-item-label']} type="info">
                              左距
                            </ElText>
                            <div class={classes['params-item-content']}>
                              <ElInput
                                modelValue={this.props.style.left}
                                onUpdate:modelValue={(v) => (this.props.style.left = v)}
                                size="small"
                              ></ElInput>
                            </div>
                          </div>,
                          <div class={classes['params-item']}>
                            <ElText class={classes['params-item-label']} type="info">
                              上距
                            </ElText>
                            <div class={classes['params-item-content']}>
                              <ElInput
                                modelValue={this.props.style.top}
                                onUpdate:modelValue={(v) => (this.props.style.top = v)}
                                size="small"
                              ></ElInput>
                            </div>
                          </div>
                        ]}
                      </>
                      <div class={classes['params-item']}>
                        <ElText class={classes['params-item-label']} type="info">
                          样式JSON
                          <Tip content={'属性名需用驼峰写法'}></Tip>
                        </ElText>
                        <div class={classes['params-item-content']}>
                          <CodeEditor
                            modelValue={JSON.stringify(this.props.style, null, 4)}
                            onUpdate:modelValue={(v) => (this.props.style = JSON.parse(v))}
                          ></CodeEditor>
                        </div>
                      </div>
                      <div class={classes['params-item']}>
                        <ElText class={classes['params-item-label']} type="info">
                          动画JSON
                          <Tip
                            content={
                              '属性名需用驼峰写法；iterations值null为无限循环；时间单位为毫秒'
                            }
                          ></Tip>
                        </ElText>
                        <div class={classes['params-item-content']}>
                          <CodeEditor
                            modelValue={JSON.stringify(this.props.animation, null, 4)}
                            onUpdate:modelValue={(v) => (this.props.animation = JSON.parse(v))}
                          ></CodeEditor>
                        </div>
                      </div>
                    </ElScrollbar>
                  ]
                }}
              </ElTabPane>
              <ElTabPane label="事件">
                {{
                  default: () => [
                    <ElScrollbar>
                      <ElButton type="primary" size="small" onClick={() => addEvent(genEvent())}>
                        + 添加事件
                      </ElButton>
                      {this.props.events?.map((item, index) => (
                        <div class={classes['params-item']}>
                          <div class={classes['params-item-label']}>
                            <ElInput v-model={item.type} size="small"></ElInput>
                            <span onClick={() => removeEvent(item)}>
                              <ElIcon color="var(--el-color-danger)" size={15}>
                                <Delete></Delete>
                              </ElIcon>
                            </span>
                          </div>
                          <div class={classes['params-item-content']}>
                            <CodeEditor
                              modelValue={item.fn}
                              onUpdate:modelValue={(v) => {
                                item.fn = v || ''
                                debounceUpdateEvent(item)
                              }}
                            ></CodeEditor>
                            <Tip content={'可访问的变量：节点自身_self；全局状态变量state'}></Tip>
                          </div>
                        </div>
                      ))}
                    </ElScrollbar>
                  ]
                }}
              </ElTabPane>
            </ElTabs>
          </>
        )
      })
    )
  }

  // props.style从element更新
  updateStyle() {
    if (!this.elRef.value) return

    const style = this.props.style
    const cStyle = this.elRef.value.style

    const propertyArr = ['width', 'height', 'left', 'top', 'transform']

    for (let property of propertyArr) {
      style[property] = cStyle.getPropertyValue(property)
    }
  }

  traverse(callback) {
    callback(this)

    const children = this.children

    for (let i = 0, l = children.length; i < l; i++) {
      children[i].traverse(callback)
    }
  }

  traverseAncestors(callback) {
    const parent = this.parent

    if (parent) {
      callback(parent)

      parent.traverseAncestors(callback)
    }
  }
  // 检查子元素是否为祖先元素
  checkChild(child) {
    let result = true

    this.traverseAncestors((node) => node === child && (result = false))

    return result
  }

  add(child) {
    if (!this.checkChild(child)) return

    if (child.parent) child.parent.remove(child)

    child.parent = this

    this.children.push(child)
  }

  insert(child, referenceChild, position = 'before') {
    if (!this.checkChild(child) || child === referenceChild) return

    if (child.parent) child.parent.remove(child)

    const index = this.children.indexOf(referenceChild)

    if (index == -1) return

    child.parent = this

    if (position === 'before') this.children.splice(index, 0, child)
    else if (position === 'after') this.children.splice(index + 1, 0, child)
  }

  remove(child) {
    child.parent = null

    const index = this.children.indexOf(child)

    if (index !== -1) {
      this.children.splice(index, 1)
    }
  }
  // 从目标节点复制参数
  copy(node) {
    Object.assign(this.props, JSON.parse(JSON.stringify(toRaw(node.props))))
  }

  clone() {
    const Constructor: any = this.constructor
    const node = new Constructor()
    node.copy(this)
    this.children.forEach((child) => node.add(child.clone()))

    return node
  }

  destroy() {}
  // 导出vue单文件组件
  toVue(): SfcObj {
    const name = this.constructor.name.replace(/^N?(\w)/, (match, p1) => p1.toLowerCase())
    const [uuid0, uuid1] = this.uuid.split('-')
    const className = `${camel2Kebab(name)}-${uuid1}`
    const watchShowObj = this.props.watchState.find((obj) => obj.type == 'show')

    const sfcObj: SfcObj = {
      name,
      uuid: this.uuid,
      imports: {
        ['./imports/store.js']: {
          state: 'state'
        }
      },
      importFiles: {
        'store.js': storeRaw
      },
      script: `
      `,
      template: `
<div${watchShowObj ? ` v-show="state.${watchShowObj.stateProperty}"` : ''} class="${className}">
  <slot></slot>
</div>
      `,
      style: `
.${className} ${obj2Style(toRaw(this.props.style))}
      `
    }

    return sfcObj
  }
}
